// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#include <linux/cdev.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/amlogic/efuse.h>
#include <linux/io.h>
#include "efuse.h"

#define EFUSE_DEVICE_NAME "efuse"
#define EFUSE_CLASS_NAME "efuse"

static struct aml_efuse_key efuse_key = {
    .num = 0,
    .infos = NULL,
};

static struct aml_efuse_lockable_check efusecheck = {
    .main_cmd = 0,
    .item_num = 0,
    .infos = NULL,
};

struct aml_efuse_cmd efuse_cmd;
unsigned int efuse_pattern_size;

#define DEFINE_EFUEKEY_SHOW_ATTR(keyname)                                                                              \
    do {                                                                                                               \
        static ssize_t keyname##_show(struct class *cla, struct class_attribute *attr, char *buf)                      \
        {                                                                                                              \
            ssize_t ret;                                                                                               \
                                                                                                                       \
            ret = efuse_user_attr_show(#keyname, buf);                                                                 \
            return ret;                                                                                                \
        }                                                                                                              \
    } while (0)

DEFINE_EFUEKEY_SHOW_ATTR(mac)
DEFINE_EFUEKEY_SHOW_ATTR(mac_bt)
DEFINE_EFUEKEY_SHOW_ATTR(mac_wifi)
DEFINE_EFUEKEY_SHOW_ATTR(usid)

#ifndef EFUSE_READ_ONLY
#define DEFINE_EFUEKEY_STORE_ATTR(keyname)                                                                             \
    do {                                                                                                               \
        static ssize_t keyname##_store(struct class *cla, struct class_attribute *attr, const char *buf, size_t count) \
        {                                                                                                              \
            ssize_t ret;                                                                                               \
                                                                                                                       \
            ret = efuse_user_attr_store(#keyname, buf, count);                                                         \
            return ret;                                                                                                \
        }                                                                                                              \
    } while (0)
DEFINE_EFUEKEY_STORE_ATTR(mac)
DEFINE_EFUEKEY_STORE_ATTR(mac_bt)
DEFINE_EFUEKEY_STORE_ATTR(mac_wifi)
DEFINE_EFUEKEY_STORE_ATTR(usid)
#endif

int efuse_getinfo(char *item, struct efusekey_info *info)
{
    int i;
    int ret = -EINVAL;

    for (i = 0; i < efuse_key.num; i++) {
        if (strcmp(efuse_key.infos[i].keyname, item) == 0) {
            strcpy(info->keyname, efuse_key.infos[i].keyname);
            info->offset = efuse_key.infos[i].offset;
            info->size = efuse_key.infos[i].size;
            ret = 0;
            break;
        }
    }
    if (ret < 0) {
        pr_err("%s item not found.\n", item);
    }
    return ret;
}

/* return: 0:is configurated, -1: don't cfg */
int efuse_burn_lockable_is_cfg(char *itemname)
{
    int ret = -1;
    int i;

    for (i = 0; i < efusecheck.item_num; i++) {
        if (strcmp(itemname, efusecheck.infos[i].itemname) == 0) {
            ret = 0;
            break;
        }
    }
    return ret;
}

/*
 * retrun: 1:burned(wrote), 0: not write, -1: fail
 */
int efuse_burn_check_burned(char *itemname)
{
    int i;
    int ret = -1;
    int subcmd;

    for (i = 0; i < efusecheck.item_num; i++) {
        if (strcmp(itemname, efusecheck.infos[i].itemname) == 0) {
            subcmd = efusecheck.infos[i].subcmd;
            ret = efuse_amlogic_check_lockable_item(subcmd);
            break;
        }
    }
    return ret;
}

static int efuse_open(struct inode *inode, struct file *file)
{
    struct aml_efuse_dev *devp;

    devp = container_of(inode->i_cdev, struct aml_efuse_dev, cdev);
    file->private_data = devp;

    return 0;
}

static int efuse_release(struct inode *inode, struct file *file)
{
    return 0;
}

static loff_t efuse_llseek(struct file *filp, loff_t off, int whence)
{
    loff_t newpos;
    ssize_t max_size;

    max_size = efuse_get_max();
    if (max_size <= 0) {
        return -EINVAL;
    }

    switch (whence) {
        case 0: /* SEEK_SET */
            newpos = off;
            break;

        case 1: /* SEEK_CUR */
            newpos = filp->f_pos + off;
            break;

        case 2L: /* SEEK_END */
            newpos = max_size + off;
            break;

        default: /* can't happen */
            return -EINVAL;
    }

    if (newpos < 0 || newpos >= max_size) {
        return -EINVAL;
    }

    filp->f_pos = newpos;
    return newpos;
}

static long efuse_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    void __user *argp = (void __user *)arg;
    struct efusekey_info info;

    switch (cmd) {
        case EFUSE_INFO_GET:
            if (copy_from_user((void *)&info, argp, sizeof(info))) {
                pr_err("%s: copy_from_user fail\n", __func__);
                return -EFAULT;
            }

            if (efuse_getinfo(info.keyname, &info) < 0) {
                pr_err("efuse: %s is not found\n", info.keyname);
                return -EFAULT;
            }

            if (copy_to_user(argp, &info, sizeof(info))) {
                pr_err("%s: copy_to_user fail\n", __func__);
                return -EFAULT;
            }
            break;

        default:
            return -ENOTTY;
    }
    return 0;
}

#ifdef CONFIG_COMPAT
static long efuse_compat_ioctl(struct file *filp, unsigned int cmd, unsigned long args)
{
    long ret;

    args = (unsigned long)compat_ptr(args);
    ret = efuse_unlocked_ioctl(filp, cmd, args);

    return ret;
}
#endif

static ssize_t efuse_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    ssize_t ret;
    unsigned int pos = (unsigned int)*ppos;
    unsigned char *op = NULL;
    unsigned int max_size;

    ret = efuse_get_max();
    if (ret < 0) {
        pr_err("efuse: failed to get userdata max size\n");
        goto exit;
    }
    max_size = (unsigned int)ret;

    if (pos >= max_size || count > max_size || count > max_size - pos) {
        ret = -EINVAL;
        pr_err("efuse: data range over userdata range\n");
        goto exit;
    }
    op = kzalloc((sizeof(char) * count), GFP_KERNEL);
    if (!op) {
        ret = -ENOMEM;
        pr_err("efuse: failed to allocate memory\n");
        goto exit;
    }

    memset(op, 0, count);

    ret = efuse_read_usr(op, count, ppos);
    if (ret < 0) {
        kfree(op);
        pr_err("efuse: read user data fail!\n");
        goto exit;
    }

    ret = copy_to_user((void *)buf, (void *)op, count);
    kfree(op);

    if (ret) {
        ret = -EFAULT;
        pr_err("%s: copy_to_user fail!!\n", __func__);
        goto exit;
    }

    ret = count;

exit:
    return ret;
}

static ssize_t efuse_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
#ifndef EFUSE_READ_ONLY
    ssize_t ret;
    unsigned int pos = (unsigned int)*ppos;
    unsigned char *op = NULL;
    unsigned int max_size;

    ret = efuse_get_max();
    if (ret < 0) {
        pr_err("efuse: failed to get userdata max size\n");
        goto exit;
    }
    max_size = (unsigned int)ret;

    if (pos >= max_size || count > max_size || count > max_size - pos) {
        ret = -EINVAL;
        pr_err("efuse: data range over userdata range\n");
        goto exit;
    }

    op = kzalloc((sizeof(char) * count), GFP_KERNEL);
    if (!op) {
        ret = -ENOMEM;
        pr_err("efuse: failed to allocate memory\n");
        goto exit;
    }

    memset(op, 0, count);

    if (copy_from_user((void *)op, (void *)buf, count)) {
        kfree(op);
        ret = -EFAULT;
        pr_err("%s: copy_from_user fail!!\n", __func__);
        goto exit;
    }

    ret = efuse_write_usr(op, count, ppos);
    kfree(op);

    if (ret < 0) {
        pr_err("efuse: write user area %d bytes data fail\n", (unsigned int)count);
        goto exit;
    }

    pr_info("efuse: write user area %d bytes data OK\n", (unsigned int)ret);

    ret = count;

exit:
    return ret;
#else
    pr_err("no permission to write!!\n");
    return -EPERM;
#endif
}

static const struct file_operations efuse_fops = {
    .owner = THIS_MODULE,
    .llseek = efuse_llseek,
    .open = efuse_open,
    .release = efuse_release,
    .read = efuse_read,
    .write = efuse_write,
    .unlocked_ioctl = efuse_unlocked_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl = efuse_compat_ioctl,
#endif
};

ssize_t efuse_user_attr_store(char *name, const char *buf, size_t count)
{
#ifndef EFUSE_READ_ONLY
    char *op = NULL;
    ssize_t ret;
    int i;
    const char *c, *s;
    struct efusekey_info info;
    unsigned int uint_val;
    loff_t pos;

    if (efuse_getinfo(name, &info) < 0) {
        ret = -EINVAL;
        pr_err("efuse: %s is not found\n", name);
        goto exit;
    }

    op = kzalloc(sizeof(char) * count, GFP_KERNEL);
    if (!op) {
        ret = -ENOMEM;
        pr_err("efuse: failed to allocate memory\n");
        goto exit;
    }

    memset(op, 0, count);
    memcpy(op, buf, count);

    c = ":";
    s = op;
    if (strstr(s, c)) {
        for (i = 0; i < info.size; i++) {
            uint_val = 0;
            if (i != info.size - 1) {
                ret = sscanf(s, "%x:", &uint_val);
                if (ret != 1 || uint_val > 0xff) {
                    ret = -EINVAL;
                }
            } else {
                ret = kstrtou8(s, 16L, (unsigned char *)&uint_val);
            }

            if (ret < 0) {
                kfree(op);
                pr_err("efuse: get key input data fail\n");
                goto exit;
            }

            op[i] = uint_val;

            s += 2L;
            if (!strncmp(s, c, 1)) {
                s++;
            }
        }
    } else if ((op[count - 1] != 0x0A && count != info.size) || count - 1 > info.size || count < info.size) {
        kfree(op);
        ret = -EINVAL;
        pr_err("efuse: key data length not match\n");
        goto exit;
    }

    pos = ((loff_t)(info.offset)) & 0xffffffff;
    ret = efuse_write_usr(op, info.size, &pos);
    kfree(op);

    if (ret < 0) {
        pr_err("efuse: write user area %d bytes data fail\n", (unsigned int)info.size);
        goto exit;
    }

    pr_info("efuse: write user area %d bytes data OK\n", (unsigned int)ret);

    ret = count;

exit:
    return ret;
#else
    pr_err("no permission to write!!\n");
    return -EPERM;
#endif
}
EXPORT_SYMBOL(efuse_user_attr_store);

ssize_t efuse_user_attr_show(char *name, char *buf)
{
    char *op = NULL;
    ssize_t ret;
    ssize_t len = 0;
    struct efusekey_info info;
    int i;
    loff_t pos;

    if (efuse_getinfo(name, &info) < 0) {
        ret = -EINVAL;
        pr_err("efuse: %s is not found\n", name);
        goto exit;
    }

    op = kzalloc(sizeof(char) * info.size, GFP_KERNEL);
    if (!op) {
        ret = -ENOMEM;
        pr_err("efuse: failed to allocate memory\n");
        goto exit;
    }

    memset(op, 0, info.size);

    pos = ((loff_t)(info.offset)) & 0xffffffff;
    ret = efuse_read_usr(op, info.size, &pos);
    if (ret < 0) {
        kfree(op);
        pr_err("efuse: read user data fail!\n");
        goto exit;
    }

    for (i = 0; i < info.size; i++) {
        if (i != 0 && (i % 16L == 0)) {
            len += sprintf(buf + len, "\n");
        }
        if (i % 16L == 0) {
            len += sprintf(buf + len, "0x%02x: ", i);
        }

        len += sprintf(buf + len, "%02x ", op[i]);
    }
    len += sprintf(buf + len, "\n");
    kfree(op);

    ret = len;

exit:
    return ret;
}

ssize_t efuse_user_attr_read(char *name, char *buf)
{
    char *op = NULL;
    ssize_t ret;
    struct efusekey_info info;
    loff_t pos;

    if (efuse_getinfo(name, &info) < 0) {
        ret = -EINVAL;
        pr_err("efuse: %s is not found\n", name);
        goto exit;
    }

    op = kzalloc(sizeof(char) * info.size, GFP_KERNEL);
    if (!op) {
        ret = -ENOMEM;
        pr_err("efuse: failed to allocate memory\n");
        goto exit;
    }

    memset(op, 0, info.size);

    pos = ((loff_t)(info.offset)) & 0xffffffff;
    ret = efuse_read_usr(op, info.size, &pos);
    if (ret < 0) {
        kfree(op);
        pr_err("efuse: read user data fail!\n");
        goto exit;
    }

    memcpy(buf, op, info.size);
    kfree(op);

    ret = (ssize_t)info.size;

exit:
    return ret;
}

static ssize_t userdata_show(struct class *cla, struct class_attribute *attr, char *buf)
{
    char *op = NULL;
    ssize_t ret;
    ssize_t len = 0;
    int i;
    loff_t offset = 0;
    unsigned int max_size;

    ret = efuse_get_max();
    if (ret < 0) {
        pr_err("efuse: failed to get userdata max size\n");
        goto exit;
    }
    max_size = (unsigned int)ret;

    op = kcalloc(max_size, sizeof(char), GFP_KERNEL);
    if (!op) {
        ret = -ENOMEM;
        pr_err("efuse: failed to allocate memory\n");
        goto exit;
    }

    memset(op, 0, max_size);

    ret = efuse_read_usr(op, max_size, &offset);
    if (ret < 0) {
        kfree(op);
        pr_err("efuse: read user data error!!\n");
        goto exit;
    }

    for (i = 0; i < ret; i++) {
        if (i != 0 && (i % 16L == 0)) {
            len += sprintf(buf + len, "\n");
        }
        if (i % 16L == 0) {
            len += sprintf(buf + len, "0x%02x: ", i);
        }

        len += sprintf(buf + len, "%02x ", op[i]);
    }
    len += sprintf(buf + len, "\n");
    kfree(op);

    ret = len;

exit:
    return ret;
}

#ifndef EFUSE_READ_ONLY
static ssize_t userdata_store(struct class *cla, struct class_attribute *attr, const char *buf, size_t count)
{
    ssize_t ret;
    loff_t offset = 0;
    char *op = NULL;
    unsigned int max_size;

    ret = efuse_get_max();
    if (ret < 0) {
        pr_err("efuse: failed to get userdata max size\n");
        goto exit;
    }
    max_size = (unsigned int)ret;

    if (count > max_size) {
        ret = -EINVAL;
        pr_err("efuse: data length over userdata max size\n");
        goto exit;
    }

    op = kzalloc(sizeof(char) * count, GFP_KERNEL);
    if (!op) {
        ret = -ENOMEM;
        pr_err("efuse: failed to allocate memory\n");
        goto exit;
    }

    memset(op, 0, count);
    memcpy(op, buf, count);

    ret = efuse_write_usr(op, count, &offset);
    kfree(op);

    if (ret < 0) {
        pr_err("efuse: write user area %d bytes data fail\n", (unsigned int)count);
        goto exit;
    }

    pr_info("efuse: write user area %d bytes data OK\n", (unsigned int)ret);

    ret = count;

exit:
    return ret;
}
#endif

static ssize_t amlogic_set_store(struct class *cla, struct class_attribute *attr, const char *buf, size_t count)
{
    ssize_t ret;
    char *op = NULL;

    if (count != efuse_pattern_size) {
        ret = -EINVAL;
        pr_err("efuse: bad pattern size, only support size %d!\n", efuse_pattern_size);
        goto exit;
    }

    op = kzalloc(sizeof(char) * count, GFP_KERNEL);
    if (!op) {
        ret = -ENOMEM;
        pr_err("efuse: failed to allocate memory!\n");
        goto exit;
    }

    memset(op, 0, count);
    memcpy(op, buf, count);

    ret = efuse_amlogic_set(op, count);
    kfree(op);

    if (ret) {
        pr_err("efuse: pattern programming fail! ret: %d\n", (unsigned int)ret);
        ret = -EINVAL;
        goto exit;
    }

    pr_info("efuse: pattern programming success!\n");

    ret = count;

exit:
    return ret;
}

static ssize_t secureboot_check_show(struct class *cla, struct class_attribute *attr, char *buf)
{
    ssize_t n = 0;
    int ret;

    struct aml_efuse_dev *efuse_dev;

    efuse_dev = container_of(cla, struct aml_efuse_dev, cls);
    if (!efuse_dev->reg_base) {
        ret = -EINVAL;
    } else {
        ret = readl(efuse_dev->reg_base) & efuse_dev->secureboot_mask;
    }
    if (ret < 0) {
        n = sprintf(buf, "fail");
    } else if (ret == 0) {
        n = sprintf(buf, "raw");
    } else {
        n = sprintf(buf, "encrypt");
    }

    return n;
}

static ssize_t checkburn_show(struct class *cla, struct class_attribute *attr, char *buf)
{
    ssize_t n = 0;
    struct aml_efuse_dev *efuse_dev;

    efuse_dev = container_of(cla, struct aml_efuse_dev, cls);
    if (efuse_dev->name[0]) {
        if (efuse_burn_lockable_is_cfg(efuse_dev->name) == 0) {
            n = efuse_burn_check_burned(efuse_dev->name);
            if (n == 1) {
                n = sprintf(buf, "wrote");
            } else if (n == 0) {
                n = sprintf(buf, "notwrite");
            } else {
                n = sprintf(buf, "error");
            }
        } else {
            n = sprintf(buf, "nocfg");
        }
    } else {
        n = sprintf(buf, "unknown");
    }
    return n;
}

static ssize_t checkburn_store(struct class *cla, struct class_attribute *attr, const char *buf, size_t count)
{
    ssize_t n = 0;
    struct aml_efuse_dev *efuse_dev;

    efuse_dev = container_of(cla, struct aml_efuse_dev, cls);

    n = strlen(buf);
    pr_notice("%s:%d %s,n=%ld\n", __func__, __LINE__, buf, n);

    n--; // discard '\n'
    if (n >= EFUSE_CHECK_NAME_LEN) {
        n = EFUSE_CHECK_NAME_LEN - 1;
    }

    memset(efuse_dev->name, 0, sizeof(efuse_dev->name));
    memcpy(efuse_dev->name, buf, n);

    return count;
}

static ssize_t checklist_show(struct class *cla, struct class_attribute *attr, char *buf)
{
    int i;
    ssize_t n = 0;

    for (i = 0; i < efusecheck.item_num; i++) {
        n += sprintf(&buf[n], efusecheck.infos[i].itemname);
        n += sprintf(&buf[n], "\n");
    }
    return n;
}

static int key_item_parse_dt(const struct device_node *np_efusekey, int index, struct efusekey_info *infos)
{
    const phandle *phandle;
    struct device_node *np_key;
    char *propname;
    const char *keyname;
    int ret;
    int name_size;

    propname = kasprintf(GFP_KERNEL, "key%d", index);

    phandle = of_get_property(np_efusekey, propname, NULL);
    if (!phandle) {
        ret = -EINVAL;
        pr_err("failed to find match %s\n", propname);
        goto exit;
    }
    np_key = of_find_node_by_phandle(be32_to_cpup(phandle));
    if (!np_key) {
        ret = -EINVAL;
        pr_err("failed to find device node %s\n", propname);
        goto exit;
    }

    ret = of_property_read_string(np_key, "keyname", &keyname);
    if (ret < 0) {
        pr_err("failed to get keyname item\n");
        goto exit;
    }

    name_size = EFUSE_KEY_NAME_LEN - 1;
    memcpy(infos[index].keyname, keyname, strlen(keyname) > name_size ? name_size : strlen(keyname));

    ret = of_property_read_u32(np_key, "offset", &infos[index].offset);
    if (ret < 0) {
        pr_err("failed to get key offset item\n");
        goto exit;
    }

    ret = of_property_read_u32(np_key, "size", &infos[index].size);
    if (ret < 0) {
        pr_err("failed to get key size item\n");
        goto exit;
    }

    pr_info("efusekey name: %15s\toffset: %5d\tsize: %5d\n", infos[index].keyname, infos[index].offset,
            infos[index].size);

    ret = 0;

exit:
    kfree(propname);
    return ret;
}

static int get_efusekey_info(struct device_node *np)
{
    const phandle *phandle;
    struct device_node *np_efusekey = NULL;
    int index;
    int ret;

    phandle = of_get_property(np, "key", NULL);
    if (!phandle) {
        ret = -EINVAL;
        pr_err("failed to find match efuse key\n");
        goto exit;
    }
    np_efusekey = of_find_node_by_phandle(be32_to_cpup(phandle));
    if (!np_efusekey) {
        ret = -EINVAL;
        pr_err("failed to find device node efusekey\n");
        goto exit;
    }

    ret = of_property_read_u32(np_efusekey, "keynum", &efuse_key.num);
    if (ret < 0) {
        pr_err("failed to get efusekey num item\n");
        goto exit;
    }

    if (efuse_key.num <= 0) {
        ret = -EINVAL;
        pr_err("efusekey num config error\n");
        goto exit;
    }
    pr_info("efusekey num: %d\n", efuse_key.num);

    efuse_key.infos = kzalloc((sizeof(struct efusekey_info)) * efuse_key.num, GFP_KERNEL);
    if (!efuse_key.infos) {
        ret = -ENOMEM;
        pr_err("fail to alloc enough mem for efusekey_infos\n");
        goto exit;
    }

    for (index = 0; index < efuse_key.num; index++) {
        ret = key_item_parse_dt(np_efusekey, index, efuse_key.infos);
        if (ret < 0) {
            kfree(efuse_key.infos);
            goto exit;
        }
    }

    return 0;

exit:
    return ret;
}

static int check_item_parse_dt(const struct device_node *np_efusecheck, int index, struct lockable_info *infos)
{
    const phandle *phandle;
    struct device_node *np_check;
    char *propname;
    const char *checkname;
    int ret;
    int name_size;

    propname = kasprintf(GFP_KERNEL, "check%d", index);

    phandle = of_get_property(np_efusecheck, propname, NULL);
    if (!phandle) {
        ret = -EINVAL;
        pr_err("failed to find match %s\n", propname);
        goto exit;
    }
    np_check = of_find_node_by_phandle(be32_to_cpup(phandle));
    if (!np_check) {
        ret = -EINVAL;
        pr_err("failed to find device node %s\n", propname);
        goto exit;
    }

    ret = of_property_read_string(np_check, "checkname", &checkname);
    if (ret < 0) {
        pr_err("failed to get checkname item\n");
        goto exit;
    }

    name_size = EFUSE_CHECK_NAME_LEN - 1;
    memcpy(infos[index].itemname, checkname, strlen(checkname) > name_size ? name_size : strlen(checkname));

    ret = of_property_read_u32(np_check, "subcmd", &infos[index].subcmd);
    if (ret < 0) {
        pr_err("failed to get subcmd item\n");
        goto exit;
    }

    pr_info("efusecheck name: %15s subcmd: 0x%16x\n", infos[index].itemname, infos[index].subcmd);

    ret = 0;

exit:
    kfree(propname);
    return ret;
}

static int get_efusecheck_info(struct device_node *np)
{
    const phandle *phandle;
    struct device_node *np_ec = NULL;
    int index;
    int ret;

    phandle = of_get_property(np, "check", NULL);
    if (!phandle) {
        ret = -EINVAL;
        pr_err("failed to find match efuse key\n");
        goto exit;
    }
    np_ec = of_find_node_by_phandle(be32_to_cpup(phandle));
    if (!np_ec) {
        ret = -EINVAL;
        pr_err("failed to find device node efusekey\n");
        goto exit;
    }

    ret = of_property_read_u32(np_ec, "maincmd", &efusecheck.main_cmd);
    if (ret < 0) {
        efusecheck.main_cmd = EFUSE_READ_CALI_ITEM;
        pr_err("don't cfg efusecheck maincmd, used default:0x%x\n", efusecheck.main_cmd);
        pr_notice("don't cfg efusecheck maincmd, used default:0x%x\n", efusecheck.main_cmd);
    } else {
        pr_info("efuse check maincmd:0x%x\n", efusecheck.main_cmd);
    }

    ret = of_property_read_u32(np_ec, "checknum", &efusecheck.item_num);
    if (ret < 0) {
        pr_err("failed to get efusecheck num item\n");
        goto exit;
    }

    if (efusecheck.item_num <= 0) {
        ret = -EINVAL;
        pr_err("efusecheck num config error\n");
        goto exit;
    }
    pr_info("efusecheck num: %d\n", efusecheck.item_num);

    efusecheck.infos = kzalloc((sizeof(struct lockable_info)) * efusecheck.item_num, GFP_KERNEL);
    if (!efusecheck.infos) {
        ret = -ENOMEM;
        pr_err("fail to alloc enough mem for efusecheck_item\n");
        goto exit;
    }

    for (index = 0; index < efusecheck.item_num; index++) {
        ret = check_item_parse_dt(np_ec, index, efusecheck.infos);
        if (ret < 0) {
            kfree(efusecheck.infos);
            goto exit;
        }
    }

    return 0;

exit:
    return ret;
}

static EFUSE_CLASS_ATTR(userdata);
static EFUSE_CLASS_ATTR(mac);
static EFUSE_CLASS_ATTR(mac_bt);
static EFUSE_CLASS_ATTR(mac_wifi);
static EFUSE_CLASS_ATTR(usid);
static CLASS_ATTR_WO(amlogic_set);
static CLASS_ATTR_RO(secureboot_check);
static EFUSE_CLASS_ATTR(checkburn);
static CLASS_ATTR_RO(checklist);

static struct attribute *efuse_calss_attrs[] = {
    &class_attr_userdata.attr,
    &class_attr_mac.attr,
    &class_attr_mac_bt.attr,
    &class_attr_mac_wifi.attr,
    &class_attr_usid.attr,
    &class_attr_amlogic_set.attr,
    &class_attr_secureboot_check.attr,
    &class_attr_checkburn.attr,
    &class_attr_checklist.attr,
    NULL,
};

ATTRIBUTE_GROUPS(efuse_calss);

static int efuse_probe(struct platform_device *pdev)
{
    int ret;
    struct device *devp;
    struct device_node *np = pdev->dev.of_node;
    struct clk *efuse_clk;
    struct aml_efuse_dev *efuse_dev;
    struct resource *reg_mem = NULL;
    void __iomem *reg_base = NULL;
    unsigned int secureboot_mask;

    efuse_dev = devm_kzalloc(&pdev->dev, sizeof(*efuse_dev), GFP_KERNEL);
    if (!efuse_dev) {
        ret = -ENOMEM;
        dev_err(&pdev->dev, "failed to alloc enough mem for efuse_dev\n");
        goto out;
    }

    efuse_dev->pdev = pdev;
    platform_set_drvdata(pdev, efuse_dev);

    efuse_clk = devm_clk_get(&pdev->dev, "efuse_clk");
    if (IS_ERR(efuse_clk)) {
        dev_err(&pdev->dev, "can't get efuse clk gate, use default clk\n");
    } else {
        ret = clk_prepare_enable(efuse_clk);
        if (ret) {
            dev_err(&pdev->dev, "failed to enable efuse clk gate\n");
            goto error1;
        }
    }

    of_node_get(np);

    ret = of_property_read_u32(np, "read_cmd", &efuse_cmd.read_cmd);
    if (ret) {
        dev_err(&pdev->dev, "please config read_cmd\n");
        goto error1;
    }

    ret = of_property_read_u32(np, "write_cmd", &efuse_cmd.write_cmd);
    if (ret) {
        dev_err(&pdev->dev, "failed to write_cmd\n");
        goto error1;
    }

    ret = of_property_read_u32(np, "get_max_cmd", &efuse_cmd.get_max_cmd);
    if (ret) {
        dev_err(&pdev->dev, "failed to get_max_cmd\n");
        goto error1;
    }

    ret = of_property_read_u32(np, "mem_in_base_cmd", &efuse_cmd.mem_in_base_cmd);
    if (ret) {
        dev_err(&pdev->dev, "failed to mem_in_base_cmd\n");
        goto error1;
    }

    ret = of_property_read_u32(np, "mem_out_base_cmd", &efuse_cmd.mem_out_base_cmd);
    if (ret) {
        dev_err(&pdev->dev, "failed to mem_out_base_cmd\n");
        goto error1;
    }

    ret = of_property_read_u32(np, "efuse_pattern_size", &efuse_pattern_size);
    if (ret) {
        efuse_pattern_size = EFUSE_PATTERN_SIZE;
        pr_err("can't get efuse_pattern_size, use default size0x%x\n", efuse_pattern_size);
    } else {
        pr_info("efuse_pattern_size:0x%x\n", efuse_pattern_size);
    }

    reg_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    if (!IS_ERR_OR_NULL(reg_mem)) {
        reg_base = devm_ioremap_resource(&pdev->dev, reg_mem);
        if (IS_ERR(reg_base)) {
            dev_err(&pdev->dev, "reg0: cannot obtain I/O memory region.\n");
            ret = PTR_ERR(reg_base);
            goto error1;
        } else {
            ret = of_property_read_u32(np, "secureboot_mask", &secureboot_mask);
            if (ret) {
                dev_err(&pdev->dev, "can't get reg secureboot_mask\n");
                goto error1;
            }
        }
    } else {
        dev_err(&pdev->dev, "can't get reg resource\n");
    }

    get_efusekey_info(np);
    get_efusecheck_info(np);

    ret = alloc_chrdev_region(&efuse_dev->devno, 0, 1, EFUSE_DEVICE_NAME);
    if (ret < 0) {
        dev_err(&pdev->dev, "fail to allocate major number\n ");
        goto error1;
    }

    efuse_dev->reg_base = reg_base;
    efuse_dev->secureboot_mask = secureboot_mask;
    efuse_dev->cls.name = EFUSE_CLASS_NAME;
    efuse_dev->cls.owner = THIS_MODULE;
    efuse_dev->cls.class_groups = efuse_calss_groups;
    ret = class_register(&efuse_dev->cls);
    if (ret) {
        goto error2;
    }

    cdev_init(&efuse_dev->cdev, &efuse_fops);
    efuse_dev->cdev.owner = THIS_MODULE;

    ret = cdev_add(&efuse_dev->cdev, efuse_dev->devno, 1);
    if (ret) {
        dev_err(&pdev->dev, "failed to add device\n");
        goto error3;
    }

    devp = device_create(&efuse_dev->cls, NULL, efuse_dev->devno, NULL, "efuse");
    if (IS_ERR(devp)) {
        dev_err(&pdev->dev, "failed to create device node\n");
        ret = PTR_ERR(devp);
        goto error4;
    }

    dev_info(&pdev->dev, "device %s created OK\n", EFUSE_DEVICE_NAME);
    return 0;

error4:
    cdev_del(&efuse_dev->cdev);
error3:
    class_unregister(&efuse_dev->cls);
error2:
    unregister_chrdev_region(efuse_dev->devno, 1);
error1:
    devm_kfree(&pdev->dev, efuse_dev);
out:
    return ret;
}

static int efuse_remove(struct platform_device *pdev)
{
    struct aml_efuse_dev *efuse_dev = platform_get_drvdata(pdev);

    unregister_chrdev_region(efuse_dev->devno, 1);
    device_destroy(&efuse_dev->cls, efuse_dev->devno);
    cdev_del(&efuse_dev->cdev);
    class_unregister(&efuse_dev->cls);
    platform_set_drvdata(pdev, NULL);

    return 0;
}

static const struct of_device_id efuse_dt_match[] = {
    {
        .compatible = "amlogic, efuse",
    },
    {},
};

static struct platform_driver efuse_driver = {
    .probe = efuse_probe,
    .remove = efuse_remove,
    .driver =
        {
            .name = EFUSE_DEVICE_NAME,
            .of_match_table = efuse_dt_match,
            .owner = THIS_MODULE,
        },
};

int __init aml_efuse_init(void)
{
    return platform_driver_register(&efuse_driver);
}

void aml_efuse_exit(void)
{
    platform_driver_unregister(&efuse_driver);
}
